﻿namespace CodeSHARPer.ViewModel
{
    using AvalonDock.Layout.Serialization;
    using CodeSHARPer.Services;
    using CodeSHARPer.ViewModel.Dialogs;
    using CodeSHARPer.ViewModel.Base;
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;
    using System.Windows.Input;
    using System.Windows.Threading;
    using CodeSHARPer.Model;
    using CodeSHARPer.ViewModel.Tools;

    partial class Workspace : ViewModelBase
    {
        #region fields
        public const string LayoutFileName = "CodeSHARPerLayout.config";

        static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
        bool? dialogCloseResult;
        bool shutDownInProgress;
        bool shutDownInProgress_Cancel;

        ObservableCollection<CodePadViewModel> openedFiles = new ObservableCollection<CodePadViewModel>();
        ReadOnlyObservableCollection<CodePadViewModel> readonyFiles;
        ObservableCollection<ToolViewModel> tools;
        RecentFilesViewModel recentFiles;
        WorkingFolderViewModel workingFolder;
        FindReplaceViewModel findAndReplace;

        private CodePadViewModel activeDocument = null;
        #endregion fields

        #region Singleton
        private static volatile Workspace instance;
        private static object syncRoot = new Object();

        #region constructor
        /// <summary>
        /// Constructor
        /// </summary>
        private Workspace()
        {
            this.dialogCloseResult = null;
            this.shutDownInProgress = shutDownInProgress_Cancel = false;
        }
        #endregion constructor

        public static Workspace Instance
        {
            get
            {
                if (instance == null)
                {
                    lock (syncRoot)
                    {
                        if (instance == null)
                            instance = new Workspace();
                    }
                }

                return instance;
            }
        }
        #endregion Sigleton

        #region Properties
        #region ActiveDocument
        public CodePadViewModel ActiveDocument
        {
            get { return activeDocument; }
            set
            {
                if (activeDocument != value)
                {
                    activeDocument = value;

                    NotifyPropertyChanged(ObservableHelper.CreateArgs<Workspace>(x => x.ActiveDocument));

                    // Ensure that no pending calls are in the dispatcher queue
                    // This makes sure that we are blocked until bindings are re-established
                    // (Bindings are, for example, required to scroll a selection into view)
                    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.SystemIdle, (Action)delegate
                    {
                        if (ActiveDocumentChanged != null)
                        {
                            ActiveDocumentChanged(this, EventArgs.Empty);

                            if (value != null && this.shutDownInProgress == false)
                                if (value.IsTempFile == true)
                                    this.Config.LastActiveFile = value.FilePath;
                        }
                    });
                }
            }
        }

        public event EventHandler ActiveDocumentChanged;

        #endregion

        public ReadOnlyObservableCollection<CodePadViewModel> Files
        {
            get
            {
                if (readonyFiles == null)
                    readonyFiles = new ReadOnlyObservableCollection<CodePadViewModel>(openedFiles);

                return readonyFiles;
            }
        }

        public ObservableCollection<ToolViewModel> Tools
        {
            get
            {
                if (tools == null)
                {
                    tools = new ObservableCollection<ToolViewModel>();
                    tools.Add(RecentFiles);
                    tools.Add(WorkingFolder);
                }

                return tools;
            }
        }

        /// <summary>
        /// This property manages the data visible in the RecentFilesViewModel.
        /// </summary>
        public RecentFilesViewModel RecentFiles
        {
            get
            {
                if (recentFiles == null)
                    recentFiles = new RecentFilesViewModel();

                return recentFiles;
            }
        }

        /// <summary>
        /// Manages the data visible in the WorkingFolderViewModel
        /// </summary>
        public WorkingFolderViewModel WorkingFolder
        {
            get
            {
                if (workingFolder == null)
                    workingFolder = new WorkingFolderViewModel();

                return workingFolder;
            }
        }

        /// <summary>
        /// Manages the data visible in the FindReplaceViewModel
        /// </summary>
        public FindReplaceViewModel FindAndReplace
        {
            get
            {
                if (findAndReplace == null)
                    findAndReplace = new FindReplaceViewModel();

                return findAndReplace;
            }
        }

        public bool ShutDownInProgress_Cancel
        {
            get { return this.shutDownInProgress_Cancel; }
            set
            {
                if (this.shutDownInProgress_Cancel != value)
                {
                    this.shutDownInProgress_Cancel = value;
                    NotifyPropertyChanged(ObservableHelper.CreateArgs<Workspace>(x => x.ShutDownInProgress_Cancel));
                }
            }
        }

        bool isToolBarVisible;
        public bool IsToolBarVisible
        {
            get { return this.isToolBarVisible; }
            set
            {
                if (this.isToolBarVisible != value)
                {
                    this.isToolBarVisible = value;
                    this.Config.IsToolBarVisible = value;
                    NotifyPropertyChanged(ObservableHelper.CreateArgs<Workspace>(x => x.IsToolBarVisible));
                }
            }
        }

        #region ApplicationName
        public string ApplicationTitle
        {
            get { return Assembly.GetEntryAssembly().GetName().Name; }
        }
        #endregion ApplicationName

        #endregion Properties

        #region methods
        /// <summary>
        /// Open a file supplied in <paramref name="filePath"/> (without displaying a file open dialog).
        /// </summary>
        /// <param name="filePath">file to open</param>
        /// <param name="AddIntoMRU">indicate whether file is to be added into MRU or not</param>
        /// <returns></returns>
        public CodePadViewModel Open(string filePath, bool AddIntoMRU = true)
        {
            // Verify whether file is already open in Editor, and if so, show it
            var fileViewModel = openedFiles.FirstOrDefault(fm => fm.FilePath == filePath);

            if (fileViewModel != null)
            {
                this.ActiveDocument = fileViewModel; // File is already open so show it

                return fileViewModel;
            }

            // try to load the file from file system
            fileViewModel = CodePadViewModel.LoadFile(filePath);

            if (fileViewModel == null)
            {
                if (this.Config.LastOpenedFiles.FindFileInList(filePath) != null)
                {
                    if (ServiceResolver.Instance.MessageBoxService.ShowYesNo(string.Format(CultureInfo.CurrentCulture,
                                           "The file:\n\n'{0}'\n\ndoes not exist or cannot be loaded.\n\nDo you want to remove this file from the list of recent files?", filePath),
                                           "Error Loading file", Cinch.CustomDialogIcons.Question) == Cinch.CustomDialogResults.Yes)
                    {
                        this.Config.LastOpenedFiles.RemoveFileInList(filePath);
                    }
                }

                return null;
            }

            this.openedFiles.Add(fileViewModel);

            // reset viewmodel options in accordance to current program settings
            SetActiveDocumentOnNewFileOrOpenFile(ref fileViewModel);

            if (AddIntoMRU == true)
                this.RecentFiles.AddFile(filePath);

            return fileViewModel;
        }

        #region NewCommand
        private void OnNew()
        {
            try
            {
                NewFileViewModel vmNewFile = new NewFileViewModel();
                bool? result = ServiceResolver.Instance.UIVisualizerService.ShowDialog("NewFileView", vmNewFile);
                if (result.GetValueOrDefault())
                {
                    ICSharpCode.CodeCompletion.CodeScriptType codeType = vmNewFile.SelectedFileType.NewFileType;
                    var vm = new CodePadViewModel(createTempFile: true, codeType: codeType);

                    this.openedFiles.Add(vm);
                    this.RecentFiles.AddFile(vm.FilePath);
                    SetActiveDocumentOnNewFileOrOpenFile(ref vm);
                }
            }
            catch (Exception exp)
            {
                logger.ErrorException(exp.Message, exp);
                ServiceResolver.Instance.MessageBoxService.ShowError(exp.Message, "Unhandled Error");
            }
        }
        #endregion NewCommand

        #region OpenCommand
        private void OnOpen()
        {
            try
            {
                ServiceResolver.Instance.OpenFileService.Filter = "CodeSHARPer files |*.csx";
                ServiceResolver.Instance.OpenFileService.InitialDirectory = this.GetDefaultPath();

                if (ServiceResolver.Instance.OpenFileService.ShowDialog(null).GetValueOrDefault())
                    this.Open(ServiceResolver.Instance.OpenFileService.FileName);
            }
            catch (Exception exp)
            {
                logger.ErrorException(exp.Message, exp);
                ServiceResolver.Instance.MessageBoxService.ShowError(exp.Message, App.IssueTrackerTitle);
            }
        }
        #endregion OnOpen

        #region Application_Exit_Command
        private void AppExit_CommandExecuted()
        {
            try
            {
                if (this.Closing_CanExecute() == true)
                {
                    this.shutDownInProgress_Cancel = false;
                    this.OnRequestClose();
                }
            }
            catch (Exception exp)
            {
                logger.ErrorException(exp.Message, exp);
                ServiceResolver.Instance.MessageBoxService.ShowError(exp.Message, "Unhandled Error");
            }
        }
        #endregion Application_Exit_Command

        #region Application_About_Command
        private void AppAbout_CommandExecuted()
        {
            try
            {
                ServiceResolver.Instance.UIVisualizerService.ShowDialog("AboutView", new AboutViewModel());
            }
            catch (Exception exp)
            {
                logger.ErrorException(exp.Message, exp);
                ServiceResolver.Instance.MessageBoxService.ShowError(exp.Message, "Unhandled Error");
            }
        }
        #endregion Application_About_Command

        #region Application_Preferences_Command
        private void AppPreferences_CommandExecuted()
        {
            try
            {
                string optionsBackup = Services.JsonSerializer.Serialize(this.Config.CodeEditorOptions);
                CodePropertiesViewModel vm = new CodePropertiesViewModel(this.Config.CodeEditorOptions, this.Config.WorkingFolderFullPath);
                bool? result = ServiceResolver.Instance.UIVisualizerService.ShowDialog("CodePropertiesView", vm);
                if (!result.GetValueOrDefault())
                {
                    CodeEditorOptions options = Services.JsonSerializer.Deserialize<CodeEditorOptions>(optionsBackup);
                    this.Config.CodeEditorOptions = options;
                    //this.Config.CodeEditorOptions.EditorFont = options.EditorFont;
                    //this.Config.CodeEditorOptions.FontSize = options.FontSize;
                    //this.Config.CodeEditorOptions.Options = options.Options;
                    //this.Config.CodeEditorOptions.ShowLineNumbers = options.ShowLineNumbers;

                    //update values on all documents
                    foreach (var codeFile in this.Files)
                    {
                        codeFile.UpdateCodeEditorProperties();
                    }
                }
                else
                {

                }
            }
            catch (Exception exp)
            {
                logger.ErrorException(exp.Message, exp);
                ServiceResolver.Instance.MessageBoxService.ShowError(exp.Message, "Unhandled Error");
            }
        }
        #endregion Application_About_Command

        #region Application_References_Command
        private void AppReferences_CommandExecuted()
        {
            try
            {
                if (this.ActiveDocument != null)
                {
                    CodeReferencesViewModel viewModel = new CodeReferencesViewModel(this.ActiveDocument.CodeCompletion.ScriptProvider);
                    bool? result = ServiceResolver.Instance.UIVisualizerService.ShowDialog("CodeReferencesView", viewModel);

                    if (result.GetValueOrDefault() && viewModel.HasChanges)
                    {
                        //save the namspaces used
                        this.ActiveDocument.CodeCompletion.ScriptProvider.AlterUsings(viewModel.Document.Text);
                        //save the file references
                        IEnumerable<string> referencesList =
                            from r in viewModel.UsedReferences
                            where r.HasError == false
                            select r.IsIdentifiedByFullName ? r.Name : r.Location;
                        this.ActiveDocument.CodeCompletion.ScriptProvider.AlterReferences(referencesList.ToArray());
                        //init the CodeCompletion with the new references and usings
                        this.ActiveDocument.CodeCompletion = new ICSharpCode.CodeCompletion.CSharpCompletion(this.ActiveDocument.CodeCompletion.ScriptProvider);
                        //sets the document as dirty, so the user be prompted to save the changes made
                        this.ActiveDocument.IsDirty = true;
                    }
                }
            }
            catch (Exception exp)
            {
                logger.ErrorException(exp.Message, exp);
                ServiceResolver.Instance.MessageBoxService.ShowError(exp.Message, "Unhandled Error");
            }
        }
        #endregion Application_About_Command

        #region RequestClose [event]

        /// <summary>
        /// Raised when this workspace should be removed from the UI.
        /// </summary>
        public event EventHandler RequestClose;

        /// <summary>
        /// Method to be executed when user (or program) tries to close the application
        /// </summary>
        public void OnRequestClose()
        {
            try
            {
                if (this.shutDownInProgress == false)
                {
                    if (this.DialogCloseResult == null)
                        this.DialogCloseResult = true;      // Execute Close event via attached property

                    if (this.shutDownInProgress_Cancel == true)
                    {
                        this.shutDownInProgress = false;
                        this.shutDownInProgress_Cancel = false;
                        this.DialogCloseResult = null;
                    }
                    else
                    {
                        this.shutDownInProgress = true;

                        CommandManager.InvalidateRequerySuggested();

                        EventHandler handler = this.RequestClose;

                        if (handler != null)
                            handler(this, EventArgs.Empty);
                    }
                }
            }
            catch (Exception exp)
            {
                this.shutDownInProgress = false;

                logger.ErrorException(exp.Message, exp);
                ServiceResolver.Instance.MessageBoxService.ShowError(exp.Message, "Unhandled Error");
            }
        }
        #endregion // RequestClose [event]

        /// <summary>
        /// Reset file view options in accordance with current program settings
        /// whenever a new file is internally created (on File Open or New File)
        /// </summary>
        /// <param name="vm"></param>
        private void SetActiveDocumentOnNewFileOrOpenFile(ref CodePadViewModel vm)
        {
            try
            {
                ActiveDocument = vm;
            }
            catch (Exception exp)
            {
                logger.ErrorException(exp.Message, exp);
                ServiceResolver.Instance.MessageBoxService.ShowError(exp.Message, "Unhandled Error");
            }
        }

        /// <summary>
        /// The Open/SaveAs file dialog opens in the location of the currente working folder (if any).
        /// </summary>
        /// <returns></returns>
        private string GetDefaultPath()
        {
            string defaultPath = string.Empty;

            try
            {

                //gets the default path for the working folder
                if (string.IsNullOrEmpty(this.WorkingFolder.FolderPath) == false)
                    defaultPath = this.WorkingFolder.FolderPath;

                // gets the default path from curently or last active document
                if (this.ActiveDocument != null)
                    defaultPath = this.ActiveDocument.GetFilePath();

                if (string.IsNullOrEmpty(defaultPath))
                    defaultPath = this.Config.GetLastActivePath();

                //gets the My documents folder path
                if (string.IsNullOrEmpty(defaultPath))
                    defaultPath = App.MyDocumentsUserDir;
                else
                {
                    //check again the folder path
                    try
                    {
                        if (System.IO.Directory.Exists(defaultPath) == false)
                            defaultPath = App.MyDocumentsUserDir;
                    }
                    catch
                    {
                        defaultPath = App.MyDocumentsUserDir;
                    }
                }
            }
            catch (Exception exp)
            {
                logger.ErrorException(exp.Message, exp);
                ServiceResolver.Instance.MessageBoxService.ShowError(exp.Message, "Unhandled Error");
            }

            return defaultPath;
        }

        internal bool OnSave(CodePadViewModel fileToSave, bool saveAsFlag = false)
        {
            string filePath = (fileToSave == null ? string.Empty : fileToSave.FilePath);

            // Offer SaveAs file dialog if file has never been saved before (was created with new command)
            if (fileToSave != null)
                saveAsFlag = saveAsFlag | fileToSave.IsTempFile;

            try
            {
                // Execute SaveAs function
                if (string.IsNullOrEmpty(filePath) || saveAsFlag == true)
                {
                    try
                    {
                        ServiceResolver.Instance.SaveFileService.FileName = System.IO.Path.GetFileName(filePath);
                    }
                    catch { }

                    ServiceResolver.Instance.SaveFileService.InitialDirectory = this.GetDefaultPath();

                    // SaveAs file if user OK'ed it so
                    if (ServiceResolver.Instance.SaveFileService.ShowDialog(null).GetValueOrDefault() == true)
                    {
                        filePath = ServiceResolver.Instance.SaveFileService.FileName;

                        fileToSave.SaveFile(filePath);
                    }
                    else
                        return false;
                }
                else  // Execute Save function
                    fileToSave.SaveFile(filePath);

                this.RecentFiles.AddFile(filePath);

                return true;
            }
            catch (Exception Exp)
            {
                string sMsg = null;

                if (string.IsNullOrEmpty(filePath) == false)
                    sMsg = string.Format(CultureInfo.CurrentCulture, "{0}{2}has occurred while saving the file{2}: {1}", Exp.Message, filePath, Environment.NewLine);
                else
                    sMsg = string.Format(CultureInfo.CurrentCulture, "{0}{1}has occurred while saving a file", Exp.Message, Environment.NewLine);

                ServiceResolver.Instance.MessageBoxService.ShowError(sMsg, "An unexpected error");
            }

            return false;
        }

        internal bool OnCloseSaveDirtyFile(CodePadViewModel fileToClose)
        {
            if (fileToClose.IsDirty)
            {
                var res = ServiceResolver.Instance.MessageBoxService.ShowYesNoCancel(string.Format(CultureInfo.CurrentCulture, "Save changes for file '{0}'?",
                                            fileToClose.FileName), this.ApplicationTitle,
                                            Cinch.CustomDialogIcons.Question,
                                            Cinch.CustomDialogResults.Yes);

                if (res == Cinch.CustomDialogResults.Cancel)
                    return false;

                if (res == Cinch.CustomDialogResults.Yes)
                    return OnSave(fileToClose);
            }

            return true;
        }

        internal bool Close(CodePadViewModel fileToClose)
        {
            try
            {
                if (this.OnCloseSaveDirtyFile(fileToClose) == false)
                    return false;

                openedFiles.Remove(fileToClose);

                if (this.openedFiles.Count == 0)
                    this.ActiveDocument = null;
                else
                    this.ActiveDocument = this.openedFiles[0];
            }
            catch (Exception exp)
            {
                logger.ErrorException(exp.Message, exp);
                ServiceResolver.Instance.MessageBoxService.ShowError(exp.Message, "Unhandled Error");
            }

            return true;
        }

        /// <summary>
        /// This can be used to close the attached view via ViewModel
        /// 
        /// Source: http://stackoverflow.com/questions/501886/wpf-mvvm-newbie-how-should-the-viewmodel-close-the-form
        /// </summary>
        public bool? DialogCloseResult
        {
            get { return this.dialogCloseResult; }

            private set
            {
                if (this.dialogCloseResult != value)
                {
                    this.dialogCloseResult = value;
                    NotifyPropertyChanged(ObservableHelper.CreateArgs<Workspace>(x => x.DialogCloseResult));
                }
            }
        }

        /// <summary>
        /// Check if pre-requisites for closing application are available.
        /// Save session data on closing and cancel closing process if necessary.
        /// </summary>
        /// <returns>true if application is OK to proceed closing with closed, otherwise false.</returns>
        internal bool Exit_CheckConditions(object sender)
        {
            if (this.shutDownInProgress == true)
                return true;

            try
            {
                try
                {
                    App.CreateAppDataFolder();
                    this.SerializeLayout(sender); // Store the current layout for later retrieval
                }
                catch { }

                // Close all open files and make sure there are no unsaved Edits
                if (this.openedFiles != null)
                {
                    // If there are any: Ask user if Edits should be saved
                    for (int i = 0; i < this.openedFiles.Count; i++)
                    {
                        CodePadViewModel f = this.openedFiles[i];

                        if (this.OnCloseSaveDirtyFile(f) == false)
                        {
                            // Cancel shutdown process (return false) if user cancels saving Edits
                            this.shutDownInProgress = false;
                            return false;
                        }
                    }
                }
            }
            catch (Exception exp)
            {
                logger.ErrorException(exp.Message, exp);
                ServiceResolver.Instance.MessageBoxService.ShowError(exp.Message, "Unhandled Error");
            }

            return true;
        }

        internal void SerializeLayout(object sender)
        {
            //avalondock xml serializer
            XmlLayoutSerializer xmlLayout = null;
            MainWindow mainWin = null;

            if (sender != null)
                mainWin = sender as MainWindow;

            // save the dockManager Layout
            if (mainWin != null)
            {
                //if there is a hidden tool, that doesn't get visible by default, then we need to remove it
                mainWin.dockManager.Layout.Hidden.Clear();

                xmlLayout = new XmlLayoutSerializer(mainWin.dockManager);
                xmlLayout.Serialize(System.IO.Path.Combine(App.DirAppData, LayoutFileName));
            }
        }

        /// <summary>
        /// Set the active document to the file in <seealso cref="fileNamePath"/>
        /// if this is currently open.
        /// </summary>
        /// <param name="fileNamePath"></param>
        internal bool SetActiveDocument(string fileNamePath)
        {
            try
            {
                if (this.Files.Count >= 0)
                {
                    CodePadViewModel fi = this.Files.SingleOrDefault(f => f.FilePath == fileNamePath);

                    if (fi != null)
                    {
                        this.ActiveDocument = fi;
                        return true;
                    }
                }
            }
            catch (Exception exp)
            {
                logger.ErrorException(exp.Message, exp);
                ServiceResolver.Instance.MessageBoxService.ShowError(exp.Message, "Unhandled Error");
            }

            return false;
        }
        #endregion methods
    }
}
